package com.sunlands.zlcx.usercenter.service;

import com.sunlands.zlcx.usercenter.client.UserFriendsService;
import com.sunlands.zlcx.usercenter.domain.UsersDO;
import com.sunlands.zlcx.usercenter.exception.SendExceptionUtil;
import com.sunlands.zlcx.usercenter.repository.UsersRepository;
import com.sunlands.zlcx.usercenter.util.BeanPropertiesUtil;
import com.sunlands.zlcx.usercenter.util.StringUtil;
import com.sunlands.zlcx.usercenter.vo.UsersTargetRelationVO;
import com.sunlands.zlcx.usercenter.vo.UsersVO;
import com.sunlands.zlcx.usercenter.vo.feign.TzUserBlacklist;
import com.sunlands.zlcx.usercenter.vo.response.PageResultVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.sunlands.zlcx.usercenter.config.RedisConfig.CacheKeys.USER_COORDINATE;
import static com.sunlands.zlcx.usercenter.config.RedisConfig.CacheKeys.USER_POOL_KEY;

@Service
@Slf4j
public class UserRecommendServiceImpl implements BeanPropertiesUtil {

    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    private TargetServiceImpl targetService;
    @Autowired
    private UserFriendsService tzUserFriendsService;
    @Autowired
    protected SendExceptionUtil sendExceptionUtil;
    @Autowired
    private UsersRepository usersRepository;
    @Autowired
    private UsersServiceImpl usersService;


    /**
     * 获取用户推荐列表
     *
     * @param userIdList
     * @param currentUserVO
     * @param pageSize
     * @return
     */
    public PageResultVO<UsersVO> userRecommendList(List<Long> userIdList, UsersVO currentUserVO, Integer pageSize) {

        List<UsersVO> usersStudyInfoDOPage;
        List<TzUserBlacklist> blacklist = usersService.blacklist(currentUserVO.getId());
        if (null != blacklist && blacklist.size() > 0) {
            blacklist.forEach(d -> {
                userIdList.add(d.getFriendsUserId());
            });
        }
        userIdList.add(currentUserVO.getId());

        //从用户池中获取对应用户
        usersStudyInfoDOPage = getUserListFromPool(userIdList, currentUserVO, pageSize);

        //装载相应数据
        int size = usersStudyInfoDOPage.size();
        PageResultVO<UsersVO> result = new PageResultVO<>();
        if (usersStudyInfoDOPage != null && size > 0) {
            List<UsersVO> usersVOS = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                UsersVO usersVO = usersStudyInfoDOPage.get(i);
                UsersTargetRelationVO userTarget = targetService.getUserTarget(usersVO.getId());
                if (null == userTarget) {
                    usersVO.setStudyHours(new BigDecimal(0));
                    log.info("用户没有学习目标 userId={}", usersVO.getId());
                } else {
                    usersVO.setStudyHours(userTarget.getStudyHours());
                }
                usersVOS.add(usersVO);
            }
            result.setRows(usersVOS);
            result.setTotal(size);
        }
        return result;
    }

    /**
     * 在用户池中取出对应的用户
     *
     * @param userBlackIdList
     * @param currentUserVO
     * @param pageSize
     * @return
     */
    public List<UsersVO> getUserListFromPool(List<Long> userBlackIdList, UsersVO currentUserVO, Integer pageSize) {

        List<UsersVO> usersDOList = new ArrayList<>();
        List<String> userPool = (List) redisTemplate.opsForValue().get(USER_POOL_KEY);
        if (CollectionUtils.isEmpty(userPool)) {
            log.info("获取用户池失败，刷新缓存");
            userPool = flushUserPool();

            if (CollectionUtils.isEmpty(userPool)) {
                log.error("获取用户池数据失败");
                return usersDOList;
            }
        }
        List<Long> sameTargetUserIds = new ArrayList<>();
        List<Long> diffTargetUserIds = new ArrayList<>();
        String currentUserTargetName = currentUserVO.getCurrentLearnTargetName();

        //按顺序筛选出与用户目标相同和不同的组
        for (int i = 0; i < userPool.size(); i++) {
            String userInfo = userPool.get(i);
            if (StringUtil.isEmpty(userInfo)) {
                continue;
            }
            //排除黑名单
            Long uid = Long.parseLong(userInfo.split("_")[0]);
            if (uid != null && !userBlackIdList.contains(uid)) {

                if (StringUtil.notEmpty(currentUserTargetName) &&
                        currentUserTargetName.equals(userInfo.split("_")[1])) {
                    //与用户目标相同
                    sameTargetUserIds.add(uid);
                } else {
                    //与用户目标不同或者用户没有目标
                    diffTargetUserIds.add(uid);
                }
            }
        }
        //找到坐标id,坐标id的作用是下次取的时候尽量保证不去到重复的id
        String coordinateKey = USER_COORDINATE + "_" + currentUserVO.getId();
        Integer coordinateId = (Integer) redisTemplate.opsForValue().get(coordinateKey);

        List<Long> resultIds = new ArrayList<>();
        //首先在项目目标用户中查找
        if (!CollectionUtils.isEmpty(sameTargetUserIds)) {
            if (coordinateId != null) {
                //存在坐标id 就在坐标id之后找
                int index = sameTargetUserIds.indexOf(coordinateId.longValue());
                if (index != -1) {
                    resultIds = sameTargetUserIds.subList((index + 1), sameTargetUserIds.size());
                    if (!CollectionUtils.isEmpty(resultIds)) {
                        //为保证随机性取出比pageSize多一个的元素，如果原有数量不够，则有多少取多少
                        if (resultIds.size() > (pageSize + 1)) {
                            resultIds = resultIds.subList(0, pageSize + 1);
                        }
                        //将最后一个id作为新的坐标id,更新入redis中
                        redisTemplate.opsForValue().set(coordinateKey, resultIds.get(resultIds.size() - 1));
                    }
                }
            } else {

                //不存在坐标id，挑一个id作为坐标id
                if (resultIds.size() > (pageSize + 1)) {
                    resultIds = sameTargetUserIds.subList(0, pageSize + 1);
                } else {
                    resultIds = sameTargetUserIds;
                }
                //将最后一个id作为新的坐标id,更新入redis中,保存1小时
                redisTemplate.opsForValue().set(coordinateKey, resultIds.get(resultIds.size() - 1), 1, TimeUnit.HOURS);
            }


        }
        //如果在相同目标用户中取出的，数量不满足pageSize+1,则在不同目标用户中补足
        if (resultIds.size() < (pageSize + 1)) {
            int count = (pageSize + 1) - resultIds.size();//需要补足数量
            if (!CollectionUtils.isEmpty(diffTargetUserIds)) {

                List<Long> temp = new ArrayList<>();
                if (coordinateId != null) {
                    //存在坐标id 就在坐标id之后找
                    int index = diffTargetUserIds.indexOf(coordinateId.longValue());
                    if (index != -1) {
                        temp = diffTargetUserIds.subList((index + 1), diffTargetUserIds.size());
                        if (!CollectionUtils.isEmpty(temp)) {
                            //为保证随机性取出比pageSize多一个的元素，如果原有数量不够，则有多少取多少
                            if (temp.size() > count) {
                                temp = temp.subList(0, count);
                                resultIds.addAll(temp);
                            }
                            //将最后一个id作为新的坐标id,更新入redis中
                            redisTemplate.opsForValue().set(coordinateKey, temp.get(temp.size() - 1));
                        }
                    } else {
                        //不再非同目标用户组中有坐标 就直接补足。
                        if (diffTargetUserIds.size() > count) {
                            temp = diffTargetUserIds.subList(0, count);
                        } else {
                            temp = diffTargetUserIds;
                        }
                        //将最后一个id作为新的坐标id,更新入redis中
                        redisTemplate.opsForValue().set(coordinateKey, temp.get(temp.size() - 1));
                        resultIds.addAll(temp);
                    }
                } else {
                    //不存在坐标id，挑一个id作为坐标id
                    if (diffTargetUserIds.size() > count) {
                        temp = diffTargetUserIds.subList(0, count);
                    } else {
                        temp = diffTargetUserIds;
                    }
                    //将最后一个id作为新的坐标id,更新入redis中,保存1个小时
                    redisTemplate.opsForValue().set(coordinateKey, temp.get(temp.size() - 1), 1, TimeUnit.HOURS);
                    resultIds.addAll(temp);
                }
            }

        }

        if (!CollectionUtils.isEmpty(resultIds)) {
            //如果数量不少于于pageSize+1 取出打乱循序取出pageSize个id作为结果，如果少于则打乱循序全部返回
            if (resultIds.size() >= (pageSize + 1)) {
                resultIds = resultIds.subList(0, pageSize);
            }
        }

        if (resultIds.size() > 3) {
            usersDOList = usersService.findByIdIn(resultIds);
        } else {
            usersDOList = resultIds.stream().map(id -> usersService.find(id)).collect(Collectors.toList());
        }
        if (!CollectionUtils.isEmpty(usersDOList)) {
            //打乱顺序
            Collections.shuffle(usersDOList);
        }

        return usersDOList;
    }

    /**
     * 刷新用户池缓存
     *
     * @return
     */
    public synchronized List<String> flushUserPool() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DATE, -7);
        Date yesterdayDate = cal.getTime();

        String yesterday = sdf.format(yesterdayDate);
        String today = sdf.format(new Date());

        List<UsersDO> usersDOList = usersRepository.findUsersPool(yesterday, today);
        if (!CollectionUtils.isEmpty(usersDOList)) {
            List<String> poolstr = usersDOList.stream().filter(usersDO -> StringUtil.notEmpty(usersDO.getCurrentLearnTargetName())).
                    map(u -> u.getId() + "_" + u.getCurrentLearnTargetName()).collect(Collectors.toList());
            redisTemplate.opsForValue().set(USER_POOL_KEY, poolstr);
            return poolstr;
        }
        return null;
    }


}
